 /*******************************************************************************
  * Copyright (c) 2003, 2006 IBM Corporation and others.
  * All rights reserved. This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License v1.0
  * which accompanies this distribution, and is available at
  * http://www.eclipse.org/legal/epl-v10.html
  *
  * Contributors:
  * IBM Corporation - initial API and implementation
  *******************************************************************************/
 package org.eclipse.ui.internal.navigator.extensions;

 import java.util.Iterator ;
 import java.util.Set ;
 import java.util.TreeSet ;

 import org.eclipse.core.expressions.ElementHandler;
 import org.eclipse.core.expressions.EvaluationContext;
 import org.eclipse.core.expressions.EvaluationResult;
 import org.eclipse.core.expressions.Expression;
 import org.eclipse.core.expressions.ExpressionConverter;
 import org.eclipse.core.runtime.CoreException;
 import org.eclipse.core.runtime.IConfigurationElement;
 import org.eclipse.core.runtime.IStatus;
 import org.eclipse.jface.viewers.ILabelProvider;
 import org.eclipse.jface.viewers.IStructuredSelection;
 import org.eclipse.jface.viewers.ITreeContentProvider;
 import org.eclipse.osgi.util.NLS;
 import org.eclipse.ui.IPluginContribution;
 import org.eclipse.ui.WorkbenchException;
 import org.eclipse.ui.internal.navigator.CommonNavigatorMessages;
 import org.eclipse.ui.internal.navigator.CustomAndExpression;
 import org.eclipse.ui.internal.navigator.NavigatorPlugin;
 import org.eclipse.ui.navigator.ICommonContentProvider;
 import org.eclipse.ui.navigator.ICommonLabelProvider;
 import org.eclipse.ui.navigator.INavigatorContentDescriptor;
 import org.eclipse.ui.navigator.Priority;

 /**
  * Encapsulates the <code>org.eclipse.ui.navigator.navigatorContent</code>
  * extension point.
  * <p>
  * <strong>EXPERIMENTAL</strong>. This class or interface has been added as
  * part of a work in progress. There is a guarantee neither that this API will
  * work nor that it will remain the same. Please do not use this API without
  * consulting with the Platform/UI team.
  * </p>
  *
  * @since 3.2
  */
 public final class NavigatorContentDescriptor implements
         INavigatorContentDescriptor, INavigatorContentExtPtConstants {

     private static final int HASH_CODE_NOT_COMPUTED = -1;
     private String id;

     private String name;

     private IConfigurationElement configElement;

     private int priority = Priority.NORMAL_PRIORITY_VALUE;

     private Expression enablement;

     private Expression possibleChildren;

     private String icon;

     private boolean activeByDefault;

     private IPluginContribution contribution;

     private Set overridingExtensions;

     private OverridePolicy overridePolicy;

     private String suppressedExtensionId;

     private INavigatorContentDescriptor overriddenDescriptor;

     private int hashCode = HASH_CODE_NOT_COMPUTED;

     private boolean providesSaveables;

     /**
      * Creates a new content descriptor from a configuration element.
      *
      * @param configElement
      * configuration element to create a descriptor from
      *
      * @throws WorkbenchException
      * if the configuration element could not be parsed. Reasons
      * include:
      * <ul>
      * <li>A required attribute is missing.</li>
      * <li>More elements are define than is allowed.</li>
      * </ul>
      */
     /* package */ NavigatorContentDescriptor(IConfigurationElement configElement)
             throws WorkbenchException {
         super();
         this.configElement = configElement;
         init();
     }

     /*
      * (non-Javadoc)
      *
      * @see org.eclipse.ui.internal.navigator.extensions.INavigatorContentDescriptor#getId()
      */
     public String getId() {
         return id;
     }

     /*
      * (non-Javadoc)
      *
      * @see org.eclipse.ui.internal.navigator.extensions.INavigatorContentDescriptor#getName()
      */
     public String getName() {
         return name;
     }

     /*
      * (non-Javadoc)
      *
      * @see org.eclipse.ui.internal.navigator.extensions.INavigatorContentDescriptor#getPriority()
      */
     public int getPriority() {
         return priority;
     }

     /**
      * Parses the configuration element.
      *
      * @throws WorkbenchException
      * if the configuration element could not be parsed. Reasons
      * include:
      * <ul>
      * <li>A required attribute is missing.</li>
      * <li>More elements are define than is allowed.</li>
      * </ul>
      */
     private void init() throws WorkbenchException {
         id = configElement.getAttribute(ATT_ID);
         name = configElement.getAttribute(ATT_NAME);
         String priorityString = configElement.getAttribute(ATT_PRIORITY);
         icon = configElement.getAttribute(ATT_ICON);

         String activeByDefaultString = configElement
                 .getAttribute(ATT_ACTIVE_BY_DEFAULT);
         activeByDefault = (activeByDefaultString != null && activeByDefaultString
                 .length() > 0) ? Boolean.valueOf(
                 configElement.getAttribute(ATT_ACTIVE_BY_DEFAULT))
                 .booleanValue() : true;

         String providesSaveablesString = configElement
             .getAttribute(ATT_PROVIDES_SAVEABLES);
         providesSaveables = (providesSaveablesString != null && providesSaveablesString
                 .length() > 0) ? Boolean.valueOf(providesSaveablesString)
                         .booleanValue() : false;

         if (priorityString != null) {
             try {
                 Priority p = Priority.get(priorityString);
                 priority = p != null ? p.getValue()
                         : Priority.NORMAL_PRIORITY_VALUE;
             } catch (NumberFormatException exception) {
                 priority = Priority.NORMAL_PRIORITY_VALUE;
             }
         }
         if (id == null) {
             throw new WorkbenchException(NLS.bind(
                     CommonNavigatorMessages.Attribute_Missing_Warning,
                     new Object [] {
                             ATT_ID,
                             id,
                             configElement.getDeclaringExtension()
                                     .getNamespaceIdentifier() }));
         }

         IConfigurationElement[] children = configElement
                 .getChildren(TAG_ENABLEMENT);
         if (children.length == 0) {

             children = configElement.getChildren(TAG_TRIGGER_POINTS);
             if (children.length == 1) {
                 enablement = new CustomAndExpression(children[0]);
             } else {
                 throw new WorkbenchException(NLS.bind(
                         CommonNavigatorMessages.Attribute_Missing_Warning,
                         new Object [] {
                                 TAG_TRIGGER_POINTS,
                                 id,
                                 configElement.getDeclaringExtension()
                                         .getNamespaceIdentifier() }));
             }

             children = configElement.getChildren(TAG_POSSIBLE_CHILDREN);
             if (children.length == 1) {
                 possibleChildren = new CustomAndExpression(children[0]);
             } else if(children.length > 1){
                 throw new WorkbenchException(NLS.bind(
                         CommonNavigatorMessages.Attribute_Missing_Warning,
                         new Object [] {
                                 TAG_POSSIBLE_CHILDREN,
                                 id,
                                 configElement.getDeclaringExtension()
                                         .getNamespaceIdentifier() }));
             }
         } else if (children.length == 1) {
             try {
                 enablement = ElementHandler.getDefault().create(
                         ExpressionConverter.getDefault(), children[0]);
             } catch (CoreException e) {
                 NavigatorPlugin.log(IStatus.ERROR, 0, e.getMessage(), e);
             }
         } else if (children.length > 1) {
             throw new WorkbenchException(NLS.bind(
                     CommonNavigatorMessages.Attribute_Missing_Warning,
                     new Object [] {
                             TAG_ENABLEMENT,
                             id,
                             configElement.getDeclaringExtension()
                                     .getNamespaceIdentifier() }));
         }

         contribution = new IPluginContribution() {

             public String getLocalId() {
                 return getId();
             }

             public String getPluginId() {
                 return configElement.getDeclaringExtension().getNamespaceIdentifier();
             }

         };

         children = configElement.getChildren(TAG_OVERRIDE);
         if (children.length == 1) {
             suppressedExtensionId = children[0]
                     .getAttribute(ATT_SUPPRESSED_EXT_ID);
             overridePolicy = OverridePolicy.get(children[0]
                     .getAttribute(ATT_POLICY));
         }
     }

     /**
      * @return Returns the icon.
      */
     public String getIcon() {
         return icon;
     }

     /**
      * @return Returns the suppressedExtensionId or null if none specified.
      */
     public String getSuppressedExtensionId() {
         return suppressedExtensionId;
     }

     /**
      * @return Returns the overridePolicy or null if this extension does not
      * override another extension.
      */
     public OverridePolicy getOverridePolicy() {
         return overridePolicy;
     }

     /**
      * @return Returns the contribution.
      */
     public IPluginContribution getContribution() {
         return contribution;
     }

     /**
      * The content provider could be an instance of
      * {@link ICommonContentProvider}, but only {@link ITreeContentProvider} is
      * required.
      *
      *
      * @return An instance of the Content provider defined for this extension.
      * @throws CoreException
      * if an instance of the executable extension could not be
      * created for any reason
      *
      */
     public ITreeContentProvider createContentProvider() throws CoreException {
         return (ITreeContentProvider) configElement
                 .createExecutableExtension(ATT_CONTENT_PROVIDER);
     }

     /**
      *
      * The content provider could be an instance of {@link ICommonLabelProvider},
      * but only {@link ILabelProvider} is required.
      *
      * @return An instance of the Label provider defined for this extension
      * @throws CoreException
      * if an instance of the executable extension could not be
      * created for any reason
      */
     public ILabelProvider createLabelProvider() throws CoreException {
         return (ILabelProvider) configElement
                 .createExecutableExtension(ATT_LABEL_PROVIDER);
     }

     /*
      * (non-Javadoc)
      *
      * @see org.eclipse.ui.internal.navigator.extensions.INavigatorContentDescriptor#isEnabledByDefault()
      */
     public boolean isActiveByDefault() {
         return activeByDefault;
     }

     /**
      * Determine if this content extension would be able to provide children for
      * the given element.
      *
      * @param anElement
      * The element that should be used for the evaluation.
      * @return True if and only if the extension is enabled for the element.
      */
     public boolean isTriggerPoint(Object anElement) {

         if (enablement == null || anElement == null) {
             return false;
         }

         try {
             EvaluationContext context = new EvaluationContext(null, anElement);
             context.setAllowPluginActivation(true);
             return (enablement.evaluate(context) == EvaluationResult.TRUE);
         } catch (CoreException e) {
             NavigatorPlugin.logError(0, e.getMessage(), e);
         }
         return false;
     }

     /**
      * Determine if this content extension could provide the given element as a
      * child.
      *
      * <p>
      * This method is used to determine what the parent of an element could be
      * for Link with Editor support.
      * </p>
      *
      * @param anElement
      * The element that should be used for the evaluation.
      * @return True if and only if the extension might provide an object of this
      * type as a child.
      */
     public boolean isPossibleChild(Object anElement) {

         if ((enablement == null && possibleChildren == null)
                 || anElement == null) {
             return false;
         } else if(anElement instanceof IStructuredSelection) {
             return arePossibleChildren((IStructuredSelection) anElement);
         }

         try {
             EvaluationContext context = new EvaluationContext(null, anElement);
             context.setAllowPluginActivation(true);
             if (possibleChildren != null) {
                 return (possibleChildren.evaluate(context) == EvaluationResult.TRUE);
             } else if (enablement != null) {
                 return (enablement.evaluate(context) == EvaluationResult.TRUE);
             }
         } catch (CoreException e) {
             NavigatorPlugin.logError(0, e.getMessage(), e);
         }
         return false;
     }
     
     /**
      * A convenience method to check all elements in a selection.
      *
      * @param aSelection A non-null selection
      * @return True if and only if every element in the selection is a possible child.
      */
     public boolean arePossibleChildren(IStructuredSelection aSelection) {
         if(aSelection.isEmpty()) {
             return false;
         }
         for (Iterator iter = aSelection.iterator(); iter.hasNext();) {
             Object element = iter.next();
             if(!isPossibleChild(element)) {
                 return false;
             }
         }
         return true;
     }

     /**
      *
      * Does not force the creation of the set of overriding extensions.
      *
      * @return True if this extension has overridding extensions.
      */
     public boolean hasOverridingExtensions() {
         return overridingExtensions != null && overridingExtensions.size() > 0;
     }

     /**
      * @return The set of overridding extensions (of type
      * {@link INavigatorContentDescriptor}
      */
     public Set getOverriddingExtensions() {
         if (overridingExtensions == null) {
             overridingExtensions = new TreeSet (ExtensionPriorityComparator.DESCENDING);
         }
         return overridingExtensions;
     }

     /*
      * (non-Javadoc)
      *
      * @see java.lang.Object#toString()
      */
     public String toString() {
         return "Content[" + id + ", \"" + name + "\"]"; //$NON-NLS-1$ //$NON-NLS-2$//$NON-NLS-3$
 }
     
     /* (non-Javadoc)
      * @see java.lang.Object#hashCode()
      */
     public int hashCode() {
         if (hashCode == HASH_CODE_NOT_COMPUTED) {
             String hashCodeString = configElement.getNamespaceIdentifier() + getId();
             hashCode = hashCodeString.hashCode();
             if (hashCode == HASH_CODE_NOT_COMPUTED)
                 hashCode++;
         }
         return hashCode;
     }

     /**
      * @return The descriptor of the <code>suppressedExtensionId</code> if
      * non-null.
      */
     public INavigatorContentDescriptor getOverriddenDescriptor() {
         return overriddenDescriptor;
     }

     /**
      * @param theOverriddenDescriptor
      * The overriddenDescriptor to set.
      */
     /* package */void setOverriddenDescriptor(
             INavigatorContentDescriptor theOverriddenDescriptor) {
         overriddenDescriptor = theOverriddenDescriptor;
     }

     /* (non-Javadoc)
      * @see org.eclipse.ui.navigator.INavigatorContentDescriptor#hasSaveablesProvider()
      */
     public boolean hasSaveablesProvider() {
         return providesSaveables;
     }

 }

